Jetson Xavier NX — USB 串口设备固定绑定指南
正确物理插入顺序: 
背景与问题
在 Jetson Xavier NX 上同时使用多个 USB 转串口设备时(如 STM32 下层板 + RTK GPS),系统会按照枚举顺序分配 /dev/ttyUSB0、/dev/ttyUSB1 等设备节点。这个编号不稳定:拔插顺序、上电时序、甚至 USB Hub 的状态都可能导致编号互换,进而引发程序打开了错误的串口。
本文通过 udev 规则为每个设备创建固定的符号链接,从根本上解决这一问题。
适用环境
| 项目 | 配置 |
|---|---|
| 硬件平台 | NVIDIA Jetson Xavier NX |
| 操作系统 | Ubuntu 22.04(arm64) |
| 设备 A | STM32F103 下层控制板(CH340 USB-TTL) |
| 设备 B | UM982 RTK GPS 模块(CH340 USB-TTL) |
| 驱动芯片 | 均为 CH340,idVendor=1a86,idProduct=7523 |
核心思路
两个设备使用相同的 CH340 芯片,idVendor、idProduct 完全一致,且 CH340 通常没有唯一序列号,因此无法通过 VID/PID/Serial 三元组区分。
唯一可靠的区分方式是物理 USB 端口路径(KERNELS 属性),只要每次插在同一个物理口上,路径就不会变。
操作步骤
第一步:查询设备的物理端口路径
分别插入两个设备,用 udevadm 查看各自的 USB 拓扑路径:
# 查看当前已识别的串口设备
ls /dev/ttyUSB*
# 查看某个设备的详细属性(以 ttyUSB0 为例)
udevadm info -a -n /dev/ttyUSB0 | grep -E '{idVendor}|{idProduct}|{serial}|KERNELS'输出示例(STM32,插在第一个 USB 口):
KERNELS=="ttyUSB0"
KERNELS=="1-2.4:1.0"
KERNELS=="1-2.4" ← 这就是物理端口路径
ATTRS{idProduct}=="7523"
ATTRS{idVendor}=="1a86"
KERNELS=="1-2"
ATTRS{idVendor}=="0bda"
ATTRS{idProduct}=="5411"
...输出示例(RTK GPS,插在第二个 USB 口):
KERNELS=="ttyUSB1"
KERNELS=="1-2.2:1.0"
KERNELS=="1-2.2" ← 物理端口路径
ATTRS{idVendor}=="1a86"
ATTRS{idProduct}=="7523"
...关键信息是第三行的 KERNELS 值(不带 :1.0 后缀的那一层),它代表设备挂载的物理 USB 口位置。记录下来:
| 设备 | 物理端口路径 |
|---|---|
| STM32 下层板 | 1-2.4 |
| RTK GPS (UM982) | 1-2.2 |
注意: 如果你的 NX 使用了 USB Hub,端口路径会是类似
1-2.4这样的多级形式(1-2是 Hub,.4是 Hub 上的第几个口)。不同 Hub 或不同物理口对应的路径不同,请以实际输出为准。
第二步:编写 udev 规则
创建规则文件:
sudo tee /etc/udev/rules.d/99-usb-serial.rules << 'EOF'
# STM32 下层板 (插在 USB 口 1-2.4)
SUBSYSTEM=="tty", KERNELS=="1-2.4", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="stm32", MODE="0666"
# RTK GPS UM982 (插在 USB 口 1-2.2)
SUBSYSTEM=="tty", KERNELS=="1-2.2", ATTRS{idVendor}=="1a86", ATTRS{idProduct}=="7523", SYMLINK+="rtk_gps", MODE="0666"
EOF规则逐字段解释:
| 字段 | 含义 |
|---|---|
SUBSYSTEM=="tty" | 仅匹配串口类设备 |
KERNELS=="1-2.4" | 匹配物理 USB 端口路径 |
ATTRS{idVendor}=="1a86" | CH340 芯片的厂商 ID |
ATTRS{idProduct}=="7523" | CH340 芯片的产品 ID |
SYMLINK+="stm32" | 创建 /dev/stm32 符号链接 |
MODE="0666" | 所有用户可读写,免 sudo |
第三步:重载规则并验证
# 重新加载 udev 规则
sudo udevadm control --reload-rules
# 触发规则立即生效(无需拔插设备)
sudo udevadm trigger
# 验证符号链接
ls -la /dev/stm32 /dev/rtk_gps预期输出:
lrwxrwxrwx 1 root root 7 Jun 2 xx:xx /dev/stm32 -> ttyUSB0
lrwxrwxrwx 1 root root 7 Jun 2 xx:xx /dev/rtk_gps -> ttyUSB1符号链接指向的 ttyUSBx 编号可能会变,但 /dev/stm32 和 /dev/rtk_gps 永远指向正确的物理设备。
第四步:在代码中使用固定设备名
ROS2 launch 文件或配置中,统一使用固定设备名:
# STM32 串口通信
serial_port = '/dev/stm32'
# RTK GPS 数据接收
gps_port = '/dev/rtk_gps'ROS2 launch 参数示例:
Node(
package='rtk_um982',
executable='um982_node',
parameters=[{'port': '/dev/rtk_gps', 'baud_rate': 921600}]
),
Node(
package='base_controller',
executable='stm32_node',
parameters=[{'port': '/dev/stm32', 'baud_rate': 115200}]
),排查与调试
如果符号链接没有出现,按以下顺序排查:
# 1. 确认规则文件语法无误
cat /etc/udev/rules.d/99-usb-serial.rules
# 2. 手动测试规则是否匹配
udevadm test /sys/class/tty/ttyUSB0 2>&1 | grep SYMLINK
# 3. 实时监控 udev 事件(拔插设备观察)
udevadm monitor --property
# 4. 确认物理端口路径是否与规则一致
udevadm info -a -n /dev/ttyUSB0 | grep KERNELS注意事项
端口必须固定:此方案依赖物理端口绑定,两个设备必须始终插在同一个 USB 口上,不能互换。建议在设备或线缆上做标记。
USB Hub 的影响:如果通过 USB Hub 连接,端口路径会包含 Hub 层级信息。更换 Hub 或换到 Hub 的不同口都会改变路径,需要重新查询并更新规则。
有序列号的设备可以更灵活:如果你的设备有唯一序列号(通过
udevadm info中的ATTRS{serial}查看),可以用序列号替代端口路径来匹配,这样即使换口也能识别。CH340 芯片通常没有序列号,所以本方案使用端口路径。MODE="0666" 的安全性:这个权限设置允许所有用户读写设备,适合开发环境。生产环境中建议改用用户组方式:
GROUP="dialout", MODE="0660"并确保你的用户在
dialout组中:sudo usermod -aG dialout $USER重启后自动生效:udev 规则是持久化的,写入
/etc/udev/rules.d/后重启系统也会自动加载,无需额外配置开机启动项。
